package it.uniroma2.art.owlart.utilities.transform;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTStatement;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.models.RDFSModel;
import it.uniroma2.art.owlart.models.SKOSModel;
import it.uniroma2.art.owlart.navigation.ARTStatementIterator;
import it.uniroma2.art.owlart.navigation.ARTURIResourceIterator;
import it.uniroma2.art.owlart.utilities.RDFIterators;
import it.uniroma2.art.owlart.vocabulary.RDFS;
import it.uniroma2.art.owlart.vocabulary.SKOS;

import java.util.HashSet;
import java.util.Set;

/**
 * This class implements a procedure to extract a subtree rooted on a given RDF resource. The tree may be
 * represented by {@link SKOS#BROADER} for SKOS concept schemes or {@link RDFS#SUBCLASSOF} for OWL ontologies.<br/>
 * The implementation here supposes that the input model has at least an hierarchy inference reasoner.
 * 
 * @author Manuel Fiorelli
 * @author Armando Stellato
 * 
 */
public class SubTreeExtractor<TYPE extends RDFSModel> {

	public void doExtract(TYPE sourceModel, TYPE targetModel, ARTURIResource root)
			throws ModelAccessException, ModelUpdateException {

		Set<ARTURIResource> candidateResources = collectSubResources(sourceModel, root);

		System.out.println("Number of candidate resources: " + candidateResources.size());

		candidateResources.add(root); // add the root the set

		Set<ARTResource> visited = new HashSet<ARTResource>();

		for (ARTURIResource c : candidateResources) {
			copyResource(sourceModel, targetModel, candidateResources, c, visited);
		}

		// hasTopConcept Management
		if (sourceModel instanceof SKOSModel) {
			ARTURIResourceIterator schemes = ((SKOSModel) sourceModel).listAllSchemesForConcept(root,
					NodeFilters.MAINGRAPH);
			while (schemes.streamOpen()) {
				ARTURIResource s = schemes.getNext();
				targetModel.deleteTriple(s, SKOS.Res.HASTOPCONCEPT, NodeFilters.ANY, NodeFilters.MAINGRAPH);
				targetModel.deleteTriple(NodeFilters.ANY, SKOS.Res.TOPCONCEPTOF, s, NodeFilters.MAINGRAPH);

				((SKOSModel) targetModel).setTopConcept(root, s, true, NodeFilters.MAINGRAPH);
			}
		}
	}

	private Set<ARTURIResource> collectSubResources(RDFSModel model, ARTURIResource root)
			throws ModelAccessException {
		if (model instanceof SKOSModel)
			return RDFIterators
					.getSetFromIterator(((SKOSModel) model).listNarrowerConcepts(root, true, true));
		else
			return RDFIterators.getSetFromIterator(RDFIterators.filterURIs(model.listSubClasses(root, true)));
	}

	private void copyResource(RDFSModel sourceModel, RDFSModel targetModel,
			Set<ARTURIResource> candidateConceptSet, ARTResource c, Set<ARTResource> visited)
			throws ModelAccessException, ModelUpdateException {
		ARTStatementIterator stmtIt = sourceModel.listStatements(c, NodeFilters.ANY, NodeFilters.ANY, false,
				NodeFilters.MAINGRAPH);

		visited.add(c);

		while (stmtIt.streamOpen()) {
			ARTStatement st = stmtIt.getNext();
			ARTNode object = st.getObject();

			boolean isURIResource = st.getObject().isURIResource();

			if (isURIResource
					&& !isURIResourceToBeCollected(sourceModel, candidateConceptSet, object.asURIResource()))
				continue;

			targetModel.addStatement(st, NodeFilters.MAINGRAPH);

			if (object.isResource() && !visited.contains(object)) {
				visited.add(object.asResource());
				copyResource(sourceModel, targetModel, candidateConceptSet, object.asResource(), visited);
			}
		}
		stmtIt.close();
	}

	private boolean isURIResourceToBeCollected(RDFSModel model, Set<ARTURIResource> candidateSet,
			ARTURIResource object) throws ModelAccessException {
		if (model instanceof SKOSModel) {
			if (((SKOSModel) model).isConcept(object, NodeFilters.MAINGRAPH)) {
				return candidateSet.contains(object);
			}
		}
		if (model.isClass(object, NodeFilters.MAINGRAPH)) {
			return candidateSet.contains(object);
		}
		return true;
	}

}
